001 /*
002 * Copyright 2006 Stephen J. McConnell.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
013 * implied.
014 *
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019 package net.dpml.lang;
020
021 import java.io.IOException;
022 import java.io.OutputStream;
023 import java.io.OutputStreamWriter;
024 import java.io.Writer;
025 import java.net.URI;
026
027 import javax.xml.XMLConstants;
028
029 import net.dpml.util.Logger;
030
031 /**
032 * Part datastructure.
033 *
034 * @author <a href="http://www.dpml.net">The Digital Product Meta Library</a>
035 * @version 1.0.0
036 */
037 public abstract class Part
038 {
039 /**
040 * A value encoder.
041 */
042 protected static final ValueEncoder VALUE_ENCODER = new ValueEncoder();
043
044 /**
045 * Default XML header.
046 */
047 protected static final String XML_HEADER = "<?xml version=\"1.0\"?>";
048
049 /**
050 * Part schema URN.
051 */
052 protected static final String PART_SCHEMA_URN = "link:xsd:dpml/lang/dpml-part#1.0";
053
054 /**
055 * Part header.
056 */
057 protected static final String PART_HEADER =
058 "<part xmlns=\""
059 + PART_SCHEMA_URN
060 + "\""
061 + "\n xmlns:xsi=\""
062 + XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI
063 + "\">";
064
065 /**
066 * Part footer.
067 */
068 protected static final String PART_FOOTER = "</part>";
069
070 private final Info m_info;
071 private final Classpath m_classpath;
072 private final Logger m_logger;
073
074 private transient ClassLoader m_classloader;
075 private transient String m_label;
076
077 /**
078 * Load a part from an external XML source with part caching.
079 * @param uri the external part source
080 * @return the resolved part
081 * @exception IOException of an I/O error occurs
082 */
083 public static Part load( URI uri ) throws IOException
084 {
085 return load( uri, true );
086 }
087
088 /**
089 * Load a part from an external XML source.
090 * @param uri the external part source
091 * @param cache the cache policy
092 * @return the resolved part
093 * @exception IOException of an I/O error occurs
094 */
095 public static Part load( URI uri, boolean cache ) throws IOException
096 {
097 return PartDecoder.getInstance().loadPart( uri, cache );
098 }
099
100 /**
101 * Creation of a new part datastructure.
102 * @param logger the logging channel
103 * @param info the info descriptor
104 * @param classpath the part classpath definition
105 * @exception IOException if an I/O error occurs
106 */
107 public Part( Logger logger, Info info, Classpath classpath ) throws IOException
108 {
109 this( logger, info, classpath, null );
110 }
111
112 /**
113 * Creation of a new part datastructure.
114 * @param logger the logging channel
115 * @param info the info descriptor
116 * @param classpath the part classpath definition
117 * @param label debug label
118 * @exception IOException if an I/O error occurs
119 */
120 public Part( Logger logger, Info info, Classpath classpath, String label ) throws IOException
121 {
122 super();
123 if( null == info )
124 {
125 throw new NullPointerException( "info" );
126 }
127 if( null == classpath )
128 {
129 throw new NullPointerException( "classpath" );
130 }
131 m_logger = logger;
132 m_info = info;
133 m_classpath = classpath;
134 m_label = label;
135 }
136
137 /**
138 * Return the default part content.
139 * @return the result of part instantiation
140 * @exception IOException if an IO error occurs
141 */
142 public Object getContent() throws IOException
143 {
144 try
145 {
146 return instantiate( new Object[0] );
147 }
148 catch( IOException e )
149 {
150 throw e;
151 }
152 catch( Exception e )
153 {
154 final String error =
155 "Part instantiation error.";
156 throw new PartException( error, e );
157 }
158 }
159
160 /**
161 * Return the part content or null if the result type is unresolvable
162 * relative to the supplied classes argument.
163 * @param classes the content type selection classes
164 * @return the content
165 * @exception IOException if an IO error occurs
166 */
167 public Object getContent( Class[] classes ) throws IOException
168 {
169 if( classes.length == 0 )
170 {
171 return getContent();
172 }
173 else
174 {
175 for( int i=0; i<classes.length; i++ )
176 {
177 Class c = classes[i];
178 Object content = getContent( c );
179 if( null != content )
180 {
181 return content;
182 }
183 }
184 return null;
185 }
186 }
187
188 /**
189 * Return the part content or null if the result type is unresolvable
190 * relative to the supplied classes argument. Recognized class arguments
191 * include Info, Classpath, Part, ClassLoader, and Object.
192 *
193 * @param c the content type class
194 * @return the content
195 * @exception IOException if an IO error occurs
196 */
197 protected Object getContent( Class c ) throws IOException
198 {
199 if( Info.class.isAssignableFrom( c ) )
200 {
201 return getInfo();
202 }
203 else if( Classpath.class.isAssignableFrom( c ) )
204 {
205 return getClasspath();
206 }
207 else if( Part.class.isAssignableFrom( c ) )
208 {
209 return this;
210 }
211 else if( ClassLoader.class.isAssignableFrom( c ) )
212 {
213 return getClassLoader();
214 }
215 else if( Object.class == c )
216 {
217 try
218 {
219 return instantiate( new Object[0] );
220 }
221 catch( IOException e )
222 {
223 throw e;
224 }
225 catch( Exception e )
226 {
227 final String error =
228 "Part instantiation error.";
229 throw new PartException( error, e );
230 }
231 }
232 else
233 {
234 return null;
235 }
236 }
237
238 /**
239 * Get the part info descriptor.
240 *
241 * @return the part info datastructure
242 */
243 public Info getInfo()
244 {
245 return m_info;
246 }
247
248 /**
249 * Get the part classpath definition.
250 *
251 * @return the classpath definition
252 */
253 public Classpath getClasspath()
254 {
255 return m_classpath;
256 }
257
258 /**
259 * Instantiate a value.
260 * @param args supplimentary arguments
261 * @return the resolved instance
262 * @exception Exception if a deployment error occurs
263 */
264 public abstract Object instantiate( Object[] args ) throws Exception;
265
266 /**
267 * Externalize the part to XML.
268 * @param output the output stream
269 * @exception IOException if an I/O error occurs
270 */
271 public void encode( OutputStream output ) throws IOException
272 {
273 final Writer writer = new OutputStreamWriter( output );
274 writer.write( XML_HEADER );
275 writer.write( "\n" );
276 writer.write( "\n" + PART_HEADER );
277 writer.write( "\n" );
278 encodeInfo( writer, getInfo() );
279 writer.write( "\n" );
280 encodeStrategy( writer, " " );
281 writer.write( "\n" );
282 encodeClasspath( writer, getClasspath() );
283 writer.write( "\n" );
284 writer.write( "\n" + PART_FOOTER );
285 writer.write( "\n" );
286 writer.flush();
287 output.close();
288 }
289
290 /**
291 * Test is this part is equiovalent to the supplied part.
292 *
293 * @param other the other object
294 * @return true if the parts are equivalent
295 */
296 public boolean equals( Object other )
297 {
298 if( other instanceof Part )
299 {
300 Part part = (Part) other;
301 if( !m_info.equals( part.getInfo() ) )
302 {
303 return false;
304 }
305 else
306 {
307 return m_classpath.equals( part.getClasspath() );
308 }
309 }
310 else
311 {
312 return false;
313 }
314 }
315
316 /**
317 * Get the part hashcode.
318 *
319 * @return the hash value
320 */
321 public int hashCode()
322 {
323 int hash = m_info.hashCode();
324 hash ^= m_classpath.hashCode();
325 return hash;
326 }
327
328 /**
329 * Encode this part strategy to XML.
330 *
331 * @param writer the output stream writer
332 * @param pad the character offset
333 * @exception IOException if an I/O error occurs during part externalization
334 */
335 protected abstract void encodeStrategy( Writer writer, String pad ) throws IOException;
336
337 /**
338 * Get the implementation classloader.
339 * @return the resolved classloader
340 */
341 public ClassLoader getClassLoader()
342 {
343 if( null == m_classloader )
344 {
345 m_classloader = setupClassLoader();
346 }
347 return m_classloader;
348 }
349
350 /**
351 * Get the assigned logging channel.
352 * @return the logging channel
353 */
354 protected Logger getLogger()
355 {
356 return m_logger;
357 }
358
359 private ClassLoader setupClassLoader()
360 {
361 try
362 {
363 return buildClassLoader( m_label );
364 }
365 catch( Exception e )
366 {
367 final String error =
368 "Classloader build error.";
369 throw new PartError( error, e );
370 }
371 }
372
373 private ClassLoader buildClassLoader( String label ) throws IOException
374 {
375 ClassLoader base = getAnchorClassLoader();
376 Classpath classpath = getClasspath();
377 String tag = getLabel( label );
378 return newClassLoader( base, classpath, tag );
379 }
380
381 private String getLabel( String label )
382 {
383 if( null != label )
384 {
385 return label;
386 }
387 if( null != getInfo().getTitle() )
388 {
389 return getInfo().getTitle();
390 }
391 else
392 {
393 return PartDecoder.getPartSpec( getInfo().getURI() );
394 }
395 }
396
397 private ClassLoader newClassLoader( ClassLoader base, Classpath classpath, String label ) throws IOException
398 {
399 return newClassLoader( base, classpath, label, true );
400 }
401
402 private ClassLoader newClassLoader( ClassLoader base, Classpath classpath, String label, boolean expand ) throws IOException
403 {
404 Logger logger = getLogger();
405
406 if( expand )
407 {
408 Classpath cp = classpath.getBaseClasspath();
409 if( null != cp )
410 {
411 ClassLoader cl = newClassLoader( base, cp, label + " (super)" );
412 return newClassLoader( cl, classpath, label, false );
413 }
414 }
415
416 URI[] uris = classpath.getDependencies( Category.SYSTEM );
417 if( uris.length > 0 )
418 {
419 updateSystemClassLoader( uris );
420 }
421
422 URI[] apis = classpath.getDependencies( Category.PUBLIC );
423 ClassLoader api = StandardClassLoader.buildClassLoader( logger, label, Category.PUBLIC, base, apis );
424 URI[] spis = classpath.getDependencies( Category.PROTECTED );
425 ClassLoader spi = StandardClassLoader.buildClassLoader( logger, label, Category.PROTECTED, api, spis );
426 URI[] imps = classpath.getDependencies( Category.PRIVATE );
427 return StandardClassLoader.buildClassLoader( logger, label, Category.PRIVATE, spi, imps );
428 }
429
430 private ClassLoader getAnchorClassLoader()
431 {
432 ClassLoader context = Thread.currentThread().getContextClassLoader();
433 if( null != context )
434 {
435 return context;
436 }
437 else
438 {
439 return Part.class.getClassLoader();
440 }
441 }
442
443 private void updateSystemClassLoader( URI[] uris ) throws IOException
444 {
445 ClassLoader parent = ClassLoader.getSystemClassLoader();
446 synchronized( parent )
447 {
448 if( parent instanceof SystemClassLoader )
449 {
450 SystemClassLoader loader = (SystemClassLoader) parent;
451 loader.addDelegates( uris );
452 systemExpanded( uris );
453 }
454 else
455 {
456 final String message =
457 "Cannot load ["
458 + uris.length
459 + "] system artifacts into a foreign system classloader.";
460 getLogger().debug( message );
461 }
462 }
463 }
464
465 /**
466 * Handle notification of system classloader expansion.
467 * @param uris the array of uris added to the system classloader
468 */
469 private void systemExpanded( URI[] uris )
470 {
471 if( getLogger().isDebugEnabled() )
472 {
473 StringBuffer buffer = new StringBuffer();
474 buffer.append( "system classloader expansion" );
475 for( int i=0; i<uris.length; i++ )
476 {
477 int n = i+1;
478 buffer.append( "\n [" + n + "] \t" + uris[i] );
479 }
480 getLogger().debug( buffer.toString() );
481 }
482 }
483
484 private void encodeInfo( Writer writer, Info info ) throws IOException
485 {
486 String title = info.getTitle();
487 String description = info.getDescription();
488 if( null == description )
489 {
490 if( null == title )
491 {
492 writer.write( "\n <info/>" );
493 }
494 else
495 {
496 writer.write( "\n <info title=\"" + title + "\"/>" );
497 }
498 }
499 else
500 {
501 if( null == title )
502 {
503 writer.write( "\n <info>" );
504 }
505 else
506 {
507 writer.write( "\n <info title=\"" + title + "\">" );
508 }
509 writer.write( "\n <description>" + description + "</description>" );
510 writer.write( "\n </info>" );
511 }
512 }
513
514 private void encodeClasspath( Writer writer, Classpath classpath ) throws IOException
515 {
516 writer.write( "\n <classpath>" );
517 encodeClasspathCategory( writer, classpath, Category.SYSTEM );
518 encodeClasspathCategory( writer, classpath, Category.PUBLIC );
519 encodeClasspathCategory( writer, classpath, Category.PROTECTED );
520 encodeClasspathCategory( writer, classpath, Category.PRIVATE );
521 writer.write( "\n </classpath>" );
522 }
523
524 private void encodeClasspathCategory(
525 Writer writer, Classpath classpath, Category category ) throws IOException
526 {
527 URI[] uris = classpath.getDependencies( category );
528 if( uris.length > 0 )
529 {
530 String name = category.getName();
531 writer.write( "\n <" + name + ">" );
532 for( int i=0; i<uris.length; i++ )
533 {
534 URI uri = uris[i];
535 writer.write( "\n <uri>" + uri.toASCIIString() + "</uri>" );
536 }
537 writer.write( "\n </" + name + ">" );
538 }
539 }
540 }